﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Sustainalytics.Utils
{
    /// <summary>
    /// Assemblies utilities.
    /// </summary>
    public static class AssemblyUtility
    {
        /// <summary>
        /// Get types from assemblies located in current domain assignable to a specific base type.
        /// </summary>
        /// <typeparam name="TBase"> Base type. </typeparam>
        /// <returns> Types. </returns>
        public static IEnumerable<Type> GetTypes<TBase>()
        {
            List<Type> typeLst = new List<Type>();

            // get assemblies loaded in current domain
            Assembly[] asmLst = AppDomain.CurrentDomain.GetAssemblies();

            // for each assembly get all types that match current base type
            foreach (Assembly asm in asmLst)
            {
                // get assembly types
                var asmTypes = GetTypes<TBase>(asm);

                typeLst.AddRange(asmTypes);
            }

            return typeLst;
        }


        /// <summary>
        /// Get types from assembly assignable to a specific base type.
        /// </summary>
        /// <typeparam name="TBase"> Base type. </typeparam>
        /// <param name="asm"> Assembly. </param>
        /// <returns> Types. </returns>
        public static IEnumerable<Type> GetTypes<TBase>(Assembly asm)
        {
            List<Type> typeLst = new List<Type>();

            foreach (Type type in asm.GetTypes())
            {
                if (typeof(TBase).IsAssignableFrom(type))
                {
                    typeLst.Add(type);
                }
            }

            return typeLst;
        }

        /// <summary>
        /// Get assembly names from BIN.
        /// </summary>
        /// <param name="filePattern"> File pattern. </param>
        /// <returns> Assembly names. </returns>
        public static IEnumerable<AssemblyName> GetAssemblyNamesFromBin(string filePattern)
        {
            // get bin directory
            string binPath = AppDomain.CurrentDomain.BaseDirectory;

            // get asm names
            IEnumerable<AssemblyName> asmNames = GetAssemblyNamesFromDirectory(binPath, filePattern);

            return asmNames;
        }

        /// <summary>
        /// Get assembly names from directory.
        /// </summary>
        /// <param name="dirPath"> Directory path. </param>
        /// <param name="filePattern"> File pattern. </param>
        /// <returns> Assembly names. </returns>
        public static IEnumerable<AssemblyName> GetAssemblyNamesFromDirectory(string dirPath, string filePattern)
        {
            // get all files from directory
            string[] asmFiles = Directory.GetFiles(dirPath, filePattern);

            // get asm names
            IEnumerable<AssemblyName> asmNames = GetAssemblyNamesFromFiles(asmFiles);

            return asmNames;
        }

        /// <summary>
        /// Get assembly names from assembly files.
        /// </summary>
        /// <param name="asmFiles"> Assembly files. </param>
        /// <returns> Assembly names. </returns>
        public static IEnumerable<AssemblyName> GetAssemblyNamesFromFiles(IEnumerable<string> asmFiles)
        {
            IEnumerable<AssemblyName> asmNames = asmFiles.Select(asmFile => AssemblyName.GetAssemblyName(asmFile));

            return asmNames;
        }

        /// <summary>
        /// Load all assemblies from BIN.
        /// </summary>
        /// <param name="filePattern"> File pattern. </param>
        public static void LoadAssembliesFromBin(string filePattern)
        {
            // get assembly names from BIN
            IEnumerable<AssemblyName> asmNames = GetAssemblyNamesFromBin(filePattern);

            // load asms
            LoadAssemblies(asmNames);
        }

        /// <summary>
        /// Load assemblies by AssemblyName.
        /// </summary>
        /// <param name="asmNames"> AssemblyNames. </param>
        public static void LoadAssemblies(IEnumerable<AssemblyName> asmNames)
        {
            if (asmNames == null || !asmNames.Any())
            {
                return;
            }

            foreach (AssemblyName asmName in asmNames)
            {
                if (AppDomain.CurrentDomain.GetAssemblies().Any(asm => AssemblyName.ReferenceMatchesDefinition(asmName, asm.GetName())) == false)
                {
                    Assembly.Load(asmName);
                }
            }
        }
    }
}
